//
// Created on 2023/6/7.
//
// Node APIs are not fully supported. To solve the compilation error of the interface cannot be found,
// please include "napi/native_api.h".

#include "avcodec_callback_napi.h"
#include "avcodec_log.h"
#include "avcodec_napi_utils.h"
#include "video_encoder.h"
#include <js_native_api.h>
#include <js_native_api_types.h>
#include <node_api.h>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <sys/types.h>
#include <uv.h>
namespace OHOS {
namespace Media {
bool usesafethread = true;
bool returnDirectly = true; 
std::string outPutFilePath = "/data/storage/el2/base/haps/entry/files/native_out_file.h264";
AvCodecCallbackNapi::~AvCodecCallbackNapi() {
    if (onErrorCb_env != nullptr) {
        napi_delete_reference(onErrorCb_env, onErrorCb);
    }
    if (onInputBufferAvailableCb_env != nullptr) {
        napi_delete_reference(onInputBufferAvailableCb_env, onInputBufferAvailableCb);
    }
    if (onOutputFormatChangeCb_env != nullptr) {
        napi_delete_reference(onOutputFormatChangeCb_env, onOutputFormatChangeCb);
    }
    if (onOutputBufferAvailableCb_env != nullptr) {
        napi_delete_reference(onOutputBufferAvailableCb_env, onOutputBufferAvailableCb);
    }
}
void AvCodecCallbackNapi::OnCodecErrorAsync(const ErrorCodeInfo info) const {
    int32_t errorCode = info.errorCode;
    AVCODEC_LOGD("AvCodecCallbackNapi::OnCodecErrorAsync");
    if (onErrorCb_env == nullptr || onErrorCb == nullptr)
        return;
    napi_value result;
    napi_value callback = nullptr;
    napi_value retVal;
    napi_get_undefined(onErrorCb_env, &result);
    AvCodecNapiUtils::createNapiInt32Value(onErrorCb_env, errorCode, result);
    napi_status status = napi_get_reference_value(onErrorCb_env, onErrorCb, &callback);
    napi_call_function(onErrorCb_env, nullptr, callback, ARGS1, &result, &retVal);
}
void AvCodecCallbackNapi::OnInputBufferAvailableAsync(BufferInfo &info) const {
    AVCODEC_LOGD("AvCodecCallbackNapi::codecDataArrivalAsync ");
    napi_value callbackRet = nullptr;
    napi_value callback = nullptr;
    napi_status status = napi_get_reference_value(onInputBufferAvailableCb_env, onInputBufferAvailableCb, &callback);
    status = napi_call_function(onOutputBufferAvailableCb_env, nullptr, callback, 0, nullptr, &callbackRet);
}
void AvCodecCallbackNapi::OnOutputBufferAvailableAsync(const BufferInfo &info) const {
    uint8_t *data = info.data;
    int32_t size = info.size;
    int32_t pts = info.pts;
    int32_t flags = info.flags;
    AVCODEC_LOGD("AvCodecCallbackNapi::OnOutputBufferAvailableAsync data = %d,size = %d", data, size);
    if (onOutputBufferAvailableCb_env == nullptr || onOutputBufferAvailableCb == nullptr) {
        return;
    }
    if (data != nullptr && size > 0) {
        napi_handle_scope scope;
        napi_open_handle_scope(onOutputBufferAvailableCb_env,&scope);
        if(scope == nullptr) return;
        napi_value args[ARGS4];
        napi_create_int32(onOutputBufferAvailableCb_env, size, &(args[PARAM1]));
        napi_create_int32(onOutputBufferAvailableCb_env, pts, &(args[PARAM2]));
        napi_create_int32(onOutputBufferAvailableCb_env, flags, &(args[PARAM3]));
        uint8_t *napiArrBuf = (uint8_t*)malloc(size);
        napi_status status;
        napi_value frameDataArrBuffer;
        bool ret = AvCodecNapiUtils::CreateArrayBuffer(onOutputBufferAvailableCb_env, napiArrBuf, size, &frameDataArrBuffer);
        memcpy(napiArrBuf, data, size);
        napi_create_typedarray(onOutputBufferAvailableCb_env, napi_uint8_array, size, frameDataArrBuffer, 0, &(args[PARAM0]));
        if (!ret) {
            AVCODEC_LOGE("AvCodecCallbackNapi::%s CreateArrayBuffer fail", __FUNCTION__);
            free(napiArrBuf);
            napiArrBuf = nullptr;
            napi_close_handle_scope(onOutputBufferAvailableCb_env, scope);
            return;
        }
        napi_value callbackRet = nullptr;
        napi_value callback = nullptr;
        status = napi_get_reference_value(onOutputBufferAvailableCb_env, onOutputBufferAvailableCb, &callback);
        if (status != napi_ok) {
            AVCODEC_LOGE("AvCodecCallbackNapi::%s napi_get_reference_value err[%{public}d]", __FUNCTION__, status);
            free(napiArrBuf);
            napiArrBuf = nullptr;
            napi_close_handle_scope(onOutputBufferAvailableCb_env, scope);
            return;
        }
        status = napi_call_function(onOutputBufferAvailableCb_env, nullptr, callback, ARGS4, args, &callbackRet);
        if (status != napi_ok) {
            AVCODEC_LOGE("AvCodecCallbackNapi::%s napi_call_function err[%{public}d]", __FUNCTION__, status);
        } else {
            AVCODEC_LOGD("AvCodecCallbackNapi::%s napi_call_function end", __FUNCTION__, status);
        }
        free(napiArrBuf);
        napiArrBuf = nullptr;
        napi_close_handle_scope(onOutputBufferAvailableCb_env, scope);
    }
}
void AvCodecCallbackNapi::OnOutputFormatChangedAsync(AvFormat &format) const {
    AVCODEC_LOGD("AvCodecCallbackNapi::OnOutputFormatChangedAsync");
    if (onOutputFormatChangeCb_env != nullptr || onOutputFormatChangeCb != nullptr)
        return;
    napi_value result;
    napi_value callback = nullptr;
    napi_value retVal;
    napi_get_undefined(onOutputFormatChangeCb_env, &result);
    napi_create_object(onOutputFormatChangeCb_env, &result);
    AvCodecNapiUtils::setNapiObjStringProp(onOutputFormatChangeCb_env, "mime", format.codec_mime, result);
    AvCodecNapiUtils::setNapiObjInt32Prop(onOutputFormatChangeCb_env, "encodeBitRate", format.bitrate, result);
    AvCodecNapiUtils::setNapiObjInt32Prop(onOutputFormatChangeCb_env, "width", format.width, result);
    AvCodecNapiUtils::setNapiObjInt32Prop(onOutputFormatChangeCb_env, "height", format.height, result);
    AvCodecNapiUtils::setNapiObjInt32Prop(onOutputFormatChangeCb_env, "pixelFormat", format.pixel_format, result);
    AvCodecNapiUtils::setNapiObjInt32Prop(onOutputFormatChangeCb_env, "frameRate", format.frame_rate, result);
    AvCodecNapiUtils::setNapiObjInt32Prop(onOutputFormatChangeCb_env, "iFrameIntervalMs", format.i_frame_interval, result);
    AvCodecNapiUtils::setNapiObjInt32Prop(onOutputFormatChangeCb_env, "codecQuality", format.codec_quality, result);
    AvCodecNapiUtils::setNapiObjInt32Prop(onOutputFormatChangeCb_env, "codecProfile", format.codec_profile, result);
    napi_status status = napi_get_reference_value(onOutputFormatChangeCb_env, onOutputFormatChangeCb, &callback);
    if (status != napi_ok) {
        AVCODEC_LOGE("AvCodecCallbackNapi::OnOutputFormatChangedAsync napi_get_reference_value fail(%{public}d)", status);
        return;
    }
    status = napi_call_function(onOutputFormatChangeCb_env, nullptr, callback, ARGS1, &result, &retVal);
    if (status != napi_ok) {
        AVCODEC_LOGE("AvCodecCallbackNapi::OnOutputFormatChangedAsync napi_get_reference_value fail(%{public}d)", status);
    }
}
void AvCodecCallbackNapi::setInputBufferAvailableCb(napi_env env, napi_ref callback) {
    AVCODEC_LOGD("AvCodecCallbackNapi::setInputBufferAvailableCb");
    if (usesafethread) {
            napi_value callback_vaule = nullptr;
            napi_get_reference_value(env, callback, &callback_vaule);
            napi_value async_resource_name = nullptr;
            napi_status status = napi_create_string_utf8(env, "NAPI-onOutputBufferAvailable", 128, &async_resource_name);
            if (status != napi_ok || async_resource_name == nullptr) {
                return;
            }
            status = napi_create_threadsafe_function(env, callback_vaule,
                                                     nullptr, async_resource_name,
                                                     0, 1, nullptr, nullptr,
                                                     nullptr, &outputTs, &mOutputThreadSafeFunc);
            return;
        }
    onInputBufferAvailableCb_env = env;
    onInputBufferAvailableCb = callback;
    napi_get_uv_event_loop(onInputBufferAvailableCb_env, &onInputBufferAvailableCbLoop);
    if (!onInputBufferAvailableCbLoop) {
        AVCODEC_LOGE("AvCodecCallbackNapi:setOnErrorCb() failed to get event loop");
    }
}
void AvCodecCallbackNapi::setOnErrorCb(napi_env env, napi_ref callback) {
    AVCODEC_LOGD("AvCodecCallbackNapi::setOnErrorCb");
    onErrorCb_env = env;
    onErrorCb = callback;
    napi_get_uv_event_loop(onErrorCb_env, &onErrorCbLoop);
    if (!onErrorCbLoop) {
        AVCODEC_LOGE("AvCodecCallbackNapi:setOnErrorCb() failed to get event loop");
    }
}

void AvCodecCallbackNapi::setOnOutputFormatChangeCb(napi_env env, napi_ref callback) {
    AVCODEC_LOGD("AvCodecCallbackNapi::setOnOutputFormatChangeCb");
    onOutputFormatChangeCb_env = env;
    onOutputFormatChangeCb = callback;
    napi_get_uv_event_loop(onOutputFormatChangeCb_env, &onOutputFormatChangeCbLoop);
    if (!onOutputFormatChangeCbLoop) {
        AVCODEC_LOGE("AvCodecCallbackNapi:setOnOutputFormatChangeCb() failed to get event loop");
    }
}
void AvCodecCallbackNapi::setOutputBufferAvailableCb(napi_env env, napi_ref callback) {
    //codec_fp = fopen(outPutFilePath.c_str(), "ab+");
    AVCODEC_LOGD("AvCodecCallbackNapi::setOutputBufferAvailableCb");
    if (usesafethread) {
        napi_value callback_vaule = nullptr;
        napi_get_reference_value(env, callback, &callback_vaule);
        napi_value async_resource_name = nullptr;
        napi_status status = napi_create_string_utf8(env, "NAPI-onOutputBufferAvailable", 128, &async_resource_name);
        if (status != napi_ok || async_resource_name == nullptr) {
            return;
        }
        status = napi_create_threadsafe_function(env, callback_vaule,
                                                 nullptr, async_resource_name,
                                                 0, 1, nullptr, nullptr,
                                                 nullptr, &outputTs, &mOutputThreadSafeFunc);
        return;
    }
    onOutputBufferAvailableCb_env = env;
    AVCODEC_LOGD("AvCodecCallbackNapi::setOutputBufferAvailableCb onOutputBufferAvailableCb_env size = %d", sizeof(onOutputBufferAvailableCb_env));
    onOutputBufferAvailableCb = callback;
    napi_get_uv_event_loop(onOutputBufferAvailableCb_env, &onOutputBufferAvailableCbLoop);
    if (!onOutputBufferAvailableCbLoop) {
        AVCODEC_LOGE("AvCodecCallbackNapi:setOutputBufferAvailableCb() failed to get event loop");
    }
}
void AvCodecCallbackNapi::OnCodecError(int32_t errorCode) {
    AVCODEC_LOGD("AvCodecCallbackNapi::OnCodecError errorCode = %d", errorCode);
    if (!onErrorCbLoop) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnCodecError() failed to get event loop");
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (!work) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnCodecError() failed to allocate work");
        return;
    }
    ErrorCodeInfo info;
    info.errorCode = errorCode;
    std::unique_ptr<ErrorCodeCallbackInfo> callbackInfo =
        std::make_unique<ErrorCodeCallbackInfo>(info, this);
    work->data = callbackInfo.get();
    int ret = uv_queue_work(
        onErrorCbLoop, work, [](uv_work_t *work) {}, [](uv_work_t *work, int status) {
        ErrorCodeCallbackInfo* callbackInfo = reinterpret_cast<ErrorCodeCallbackInfo *>(work->data);
        if (callbackInfo) {
            callbackInfo->callback_napi_->OnCodecErrorAsync(callbackInfo->info_);
            delete callbackInfo;
        }
        delete work; });
    if (ret) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnCodecError() failed to execute work");
        delete work;
    } else {
        callbackInfo.release();
    }
}
void AvCodecCallbackNapi::OnInputBufferAvailable(uint8_t* data, int32_t size) {
    AVCODEC_LOGD("AvCodecCallbackNapi::codecDataArrival");
    if (usesafethread) {
            napi_status napi_status = napi_call_threadsafe_function(mOutputThreadSafeFunc,
                                                                    (void *)nullptr,
                                                                    napi_tsfn_nonblocking);
            return;
        }
    if (!onInputBufferAvailableCbLoop) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnInputBufferAvailable() failed to get event loop");
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (!work) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnInputBufferAvailable() failed to allocate work");
        return;
    }

    BufferInfo info;
    std::unique_ptr<BufferCallbackInfo> callbackInfo =
        std::make_unique<BufferCallbackInfo>(info, this);
    work->data = callbackInfo.get();
    int ret = uv_queue_work(
        onInputBufferAvailableCbLoop, work, [](uv_work_t *work) {}, [](uv_work_t *work, int status) {
        BufferCallbackInfo* callbackInfo = reinterpret_cast<BufferCallbackInfo *>(work->data);
        if (callbackInfo) {
            callbackInfo->callback_napi_->OnInputBufferAvailableAsync(callbackInfo->info_);
            delete callbackInfo;
        }
        delete work; });
    if (ret) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnInputBufferAvailable() failed to execute work");
        delete work;
    } else {
        callbackInfo.release();
    }
}

void AvCodecCallbackNapi::OnOutputFormatChanged(AvFormat *format) {
    AVCODEC_LOGD("AvCodecCallbackNapi::OnOutputFormatChanged ");
    if (!onOutputFormatChangeCbLoop) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnOutputFormatChanged() failed to get event loop");
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (!work) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnOutputFormatChanged() failed to allocate work");
        return;
    }

    std::unique_ptr<AvFormatCallbackInfo> callbackInfo =
        std::make_unique<AvFormatCallbackInfo>(*format, this);
    work->data = callbackInfo.get();
    int ret = uv_queue_work(
        onOutputFormatChangeCbLoop, work, [](uv_work_t *work) {}, [](uv_work_t *work, int status) {
        AvFormatCallbackInfo* callbackInfo = reinterpret_cast<AvFormatCallbackInfo *>(work->data);
        if (callbackInfo) {
            AVCODEC_LOGD(" will called AvCodecCallbackNapi::OnOutputFormatChangedAsync");
            callbackInfo->callback_napi_->OnOutputFormatChangedAsync(callbackInfo->info_);
            delete callbackInfo;
        }
        delete work; });
    if (ret) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnOutputFormatChanged() failed to execute work");
        delete work;
    } else {
        callbackInfo.release();
    }
}

void AvCodecCallbackNapi::OnOutputBufferAvailable(uint8_t *data, int32_t size, int32_t pts, uint32_t flags) {
    AVCODEC_LOGD("AvCodecCallbackNapi::OnOutputBufferAvailable data = %d,size = %d", data, size);
    if (usesafethread) {
        auto bufferinfo = (BufferInfo *)malloc(sizeof(BufferInfo));
        bufferinfo->size = size;
        bufferinfo->pts = pts;
        bufferinfo->flags = flags;
        uint8_t *tmpbuffer = (uint8_t *)malloc(size);
        memcpy(tmpbuffer, data, size);
        bufferinfo->data = tmpbuffer;
        napi_status napi_status = napi_call_threadsafe_function(mOutputThreadSafeFunc,
                                                                (void *)bufferinfo,
                                                                napi_tsfn_nonblocking);
        if (tmpbuffer != nullptr) {
            free(tmpbuffer);
            tmpbuffer = nullptr;
        }
        return;
    }

    if (codec_fp != nullptr) {
        //fwrite(data, size, 1, codec_fp);
    }
    if (!onOutputBufferAvailableCbLoop) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnOutputBufferAvailable() failed to get event loop");
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (!work) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnOutputBufferAvailable() failed to allocate work");
        return;
    }

    BufferInfo info;
    info.data = data;
    info.size = size;
    info.pts = pts;
    info.flags = flags;
    std::unique_ptr<BufferCallbackInfo> callbackInfo =
        std::make_unique<BufferCallbackInfo>(info, this);
    work->data = callbackInfo.get();
    int ret = uv_queue_work(
        onOutputBufferAvailableCbLoop, work, [](uv_work_t *work) {}, [](uv_work_t *work, int status) {
        BufferCallbackInfo* callbackInfo = reinterpret_cast<BufferCallbackInfo *>(work->data);
        if (callbackInfo) {
            callbackInfo->callback_napi_->OnOutputBufferAvailableAsync(callbackInfo->info_);
            delete callbackInfo;
        }
        delete work; });
    if (ret) {
        AVCODEC_LOGE("AvCodecCallbackNapi:OnOutputBufferAvailable() failed to execute work");
        delete work;
    } else {
        callbackInfo.release();
    }
}

void AvCodecCallbackNapi::outputTs(napi_env env, napi_value callbackTs, void *, void *data) {
    AVCODEC_LOGD("AvCodecCallbackNapi::outputTs called");
    if (data != nullptr) {
        auto bufferinfo = reinterpret_cast<BufferInfo *>(data);
        int32_t size = bufferinfo->size;
        int32_t pts = bufferinfo->pts;
        int32_t flags = bufferinfo->flags;
        uint8_t *data = bufferinfo->data;
        napi_value args[ARGS4];
        napi_create_int32(env, size, &(args[PARAM1]));
        napi_create_int32(env, pts, &(args[PARAM2]));
        napi_create_int32(env, flags, &(args[PARAM3]));
        uint8_t *napiArrBuf = (uint8_t *)malloc(size);
        napi_status status;
        memcpy(napiArrBuf, data, size);
        data = nullptr;
        bool ret = AvCodecNapiUtils::CreateArrayBuffer(env, napiArrBuf, size, &(args[PARAM0]));
        if (!ret) {
            AVCODEC_LOGE("AvCodecCallbackNapi::%s CreateArrayBuffer fail", __FUNCTION__);
            free(napiArrBuf);
            napiArrBuf = nullptr;
            return;
        }
        napi_value callbackRet = nullptr;
        napi_value callback = nullptr;
        status = napi_call_function(env, nullptr, callbackTs, ARGS4, args, &callbackRet);
        if (status != napi_ok) {
            AVCODEC_LOGE("AvCodecCallbackNapi::%s napi_call_function err[%{public}d]", __FUNCTION__, status);
        } else {
            AVCODEC_LOGD("AvCodecCallbackNapi::%s napi_call_function end", __FUNCTION__, status);
        }
        free(napiArrBuf);
        napiArrBuf = nullptr;
    } else {
        napi_value callbackRet = nullptr;
        napi_value callback = nullptr;
//        napi_status status = napi_call_function(env, nullptr, callbackTs, 0, nullptr, &callbackRet);
    }
}

} // namespace Media
} // namespace OHOS